/*
 * SFTPSessionHandler.cpp
 *
 *  Created on: Apr 6, 2011
 *      Author: sms103
 */

#include "SFTPSessionHandler.h"

SFTPSessionHandler::SFTPSessionHandler(SFTPServer *sftpServer,SFTPConnection *conn) {
	this->sftpServer = sftpServer;
	this->ctrlConnection = conn;
	this->dataConnection = NULL;

	pthread_mutex_init(&sessionMutex,NULL);
	pthread_cond_init(&sessionCond,NULL);

	int retval = pthread_create(&this->sessionThread, NULL,
			SFTPSessionHandler::runSessionThread, (void *)this);

	if (retval < 0){
		cout << "Error starting session handler thread."<<endl;
	}
}

/* Sets the data connection for this session handler. Signals the handler's
 * thread that the data conn is ready. */
void SFTPSessionHandler::setDataConn(SFTPConnection *conn){

	pthread_mutex_lock(&sessionMutex);
	this->dataConnection = conn;
	this->fileTransporter = new FileTransporter(this->dataConnection);
	//cout << "setting data conn for ID "<<this->sessionID<<endl;
	pthread_mutex_unlock(&sessionMutex);
	pthread_cond_signal(&sessionCond);
}

/* Waits the specified timeout in milliseconds for a data connection to be
 * established. returns true if a data conn was establisged, false if we timed out.
 * Timeout of zero means return immediately whether dataConn is set or not.
 * Timeout less than zero means infinite wait.
 */
bool SFTPSessionHandler::waitForDataConn(int timeout){
	bool retval = false;

	//timespec type is required for passing to pthread_cond
	struct timespec ts;

	//timeval type for getting current time
	struct timeval  tv;

	pthread_mutex_lock(&sessionMutex);
	if(dataConnection == NULL && timeout != 0){
		if(timeout >0){
			//get current time
			gettimeofday(&tv,NULL);
			ts.tv_sec = tv.tv_sec;
			ts.tv_nsec = tv.tv_usec*1000;

			//add the timeout
			if(timeout > 1000){
				ts.tv_sec += timeout/1000;
				timeout = timeout%1000;
			}
			//big numbers are conversion factors sec/msec/nsec etc
			if(ts.tv_nsec + timeout*1000000 > 1000000000){
				ts.tv_sec +=1;
				ts.tv_nsec = (ts.tv_nsec + timeout*1000000) - 1000000000;
			}
			else ts.tv_nsec += timeout*1000000;

			//wait for an event or timeout
			int waitRetval = pthread_cond_timedwait(&sessionCond,&sessionMutex,&ts);

			if(waitRetval == 0){//dataConn was set and we were signaled, return true
				retval = true;
			}
			//else we timed out and we leave retval as false
		}

		else{//timeout is negative, we wait forever until dataconn is set(not null)
			while(!dataConnection)pthread_cond_wait(&sessionCond,&sessionMutex);

			retval = true;
		}
	}
	else if(dataConnection!=NULL){
		retval = true;
	}
	pthread_mutex_unlock(&sessionMutex);

	return retval;
}


void * SFTPSessionHandler::runSessionThread(void *p){

	SFTPSessionHandler *handler = (SFTPSessionHandler *)p;
	if(!handler->ctrlConnection->init()){
		delete(handler);
		return NULL;
	}

	SFTPMessage *message = new SFTPMessage(handler->ctrlConnection->getTransportMSS());

	handler->ctrlConnection->recvMessage(message);

	//if this is an init ctrl conn, register new session with SFTPServer, acknowledge
	//to the client you got its conn and send it its sessionID, and wait for a data conn for this session.
	if(message->getHeader()->type==MSG_TYPE_CTRL_INIT){
		handler->sessionID =  handler->sftpServer->addSession(handler);
		cout << "ctrl established, ID "<<handler->sessionID<<endl;
		message->getHeader()->attr = handler->sessionID;
		handler->ctrlConnection->sendMessage(message);

		bool dataConn = handler->waitForDataConn(DATA_CONN_WAIT_TIMEOUT * 1000);
		if(!dataConn){
			cout << "No data conn was established within the timeout for session ID "<<handler->sessionID<<
				", ending session."<<endl;
			handler->ctrlConnection->close();
		}
	}
	else if (message->getHeader()->type==MSG_TYPE_DATA_INIT){
	//if this is an init data conn, look at the sessionID sent in the message,
	//see if thats a valid session (ask SFTPServer), and give the data conn
	//to the appropriate SFTPSessionHandler (and exit this thread)
		cout << "data conn recvd session ID "<<message->getHeader()->attr<<endl;
		SFTPSessionHandler *ctrlConnHandler = handler->sftpServer->getSession(message->getHeader()->attr);
		if(ctrlConnHandler){
			ctrlConnHandler->setDataConn(handler->ctrlConnection);
			handler->ctrlConnection->sendMessage(message);
			handler->ctrlConnection=NULL;
			delete(handler);
			return NULL;
		}
		else{
			handler->ctrlConnection->close();
		}


	}
	else{//invalid type
		handler->ctrlConnection->close();
	}
	//enter state machine or command loop of some kind until session ends.
	while(handler->ctrlConnection->isOpen()){
		if(!handler->ctrlConnection->recvMessage(message)){
			handler->ctrlConnection->close();
			break;
		}
		/*
		if(message->getHeader()->type == MSG_TYPE_CLOSE){
			break;
		}
		else if(message->getHeader()->type == MSG_TYPE_TEST){
			handler->dataConnection->recvMessage(message);
			cout<<(char *)message->getData()<<endl;
		}*/
		switch(message->getHeader()->type){
		case(MSG_TYPE_CLOSE):
			//cout << "Close message received, ending sessionID "<<handler->sessionID<<endl;
			handler->ctrlConnection->close();
			break;
		case(MSG_TYPE_PUT):
				//strData of message is the path name for the file we're putting
			if(!handler->handlePut(message->getStrData())){
				handler->ctrlConnection->close();;
			}
			break;
		case(MSG_TYPE_GET):
			if(!handler->handleGet(message->getStrData())){
				handler->ctrlConnection->close();
			}
			break;
		case(MSG_TYPE_TEST):
			handler->dataConnection->recvMessage(message);
			cout<<(char *)message->getData()<<endl;
			break;
		case(MSG_TYPE_LIS):
			if(!handler->handleLis(message->getStrData())){
				handler->ctrlConnection->close();
			}
			break;
		default:
			break;
		}

	}
	if(handler->dataConnection){
		if(handler->dataConnection->isOpen()){
			handler->dataConnection->close();
		}
	}

	handler->sftpServer->deleteSession(handler->sessionID);
	cout << "Ending session "<<handler->sessionID<<endl;
	delete(handler);
	delete(message);

}


bool SFTPSessionHandler::handlePut(string filename){
	cout<< "PUTing file "<<filename<<endl;
	return fileTransporter->recvFile(filename);
}

bool SFTPSessionHandler::handleGet(string filename){
	cout<< "GETing file "<<filename<<endl;
	return fileTransporter->sendFile(filename);
}

bool SFTPSessionHandler::handleLis(string pathname){
	return fileTransporter->sendDirectoryContents(pathname);
}

SFTPSessionHandler::~SFTPSessionHandler() {
	if(ctrlConnection)delete(ctrlConnection);
	if(dataConnection)delete(dataConnection);
}
